﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Device.Location;
using System.Collections.ObjectModel;

using Microsoft.Phone.Controls.Maps;
using Microsoft.Phone.Controls.Maps.Platform;

using Utils.WP7.Bing;
using Utils.WP7.Bing.BingGeo;
using Utils.WP7.Bing.BingRoute;

namespace Utils.WP7.Bing
{
    public class RouteViewModel : INotifyPropertyChanged
    {
        #region Properties

        public static readonly string BingApiKey = "Aj6lLs-91dseTiQ6mBtM7LW7FFI6X2B1teNVXtPxEdrtZvNhW26OzC2U0N91nkfE";
        public static volatile bool IsBusy = false;

        private Location toLocation;

        private Location fromLocation;

        private Address from;
        public Address From
        {
            get { return from; }
            set
            {
                from = value;
                Change("From");
            }
        }

        private Address to;
        public Address To
        {
            get { return to; }
            set
            {
                to = value;
                Change("To");
            }
        }
        private Location startPoint;
        public Location StartPoint
        {
            get { return startPoint; }
            set
            {
                startPoint = value;
                Change("StartPoint");
            }
        }

        private Location endPoint;
        public Location EndPoint
        {
            get { return endPoint; }
            set
            {
                endPoint = value;
                Change("EndPoint");
            }
        }

        private ObservableCollection<Location> routePoints;
        public ObservableCollection<Location> RoutePoints
        {
            get { return routePoints; }
            set
            {
                routePoints = value;
                Change("RoutePoints");
            }
        }

        private TravelMode _travelMode;

        public TravelMode TravelMode
        {
            get { return _travelMode; }
            set
            {
                _travelMode = value; 
                Change("TravelMode");
            }
        }
        

        private ObservableCollection<ItineraryItem> itinerary;
        public ObservableCollection<ItineraryItem> Itinerary
        {
            get
            {
                return itinerary;
            }
            set
            {
                itinerary = value;
                Change("Itinerary");
            }
        }
        #endregion
        #region Events
        public event EventHandler RouteResolved;

        private void RaiseRouteResolved()
        {
            if (RouteResolved != null)
                RouteResolved(this, EventArgs.Empty);
        }
        #endregion

        public RouteViewModel(Address address, TravelMode mode)
        {
            
            this.To = address;
            this.to.AddressLine = address.AddressLine;
            this.to.PostalTown = address.PostalTown;
            this.to.PostalCode = address.PostalCode;
            this.to.CountryRegion = address.CountryRegion;
            this._travelMode = mode;
        }

        /// <summary>
        /// Find the route between 2 points.
        /// </summary>
        public void ResolveRoute()
        {
            GetGeoLocation(From, (l) => fromLocation = l);
            GetGeoLocation(To, (l) => toLocation = l);
        }

        // FInd the route between 2 positions.
        public void ResolveRouteFromCurrent()
        {
            // Real watcher.
            if (IsBusy)
                return;

            IsBusy = true;
            var coordinateWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
            coordinateWatcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(OnWatcherStatusChanged);
            coordinateWatcher.Start();

            // Mock watcher.
            //GeoCoordinateEventMock[] events = new GeoCoordinateEventMock[] 
            //{ 
            //    new  GeoCoordinateEventMock { Latitude = 50, Longitude = 6, Time = new TimeSpan(0,0,5) },
            //    new  GeoCoordinateEventMock { Latitude = 50, Longitude = 7, Time = new TimeSpan(0,15,0) }
            //};

            //IGeoPositionWatcher<GeoCoordinate> coordinateWatcher = new EventListGeoLocationMock(events);
            //coordinateWatcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(OnWatcherStatusChanged);
            //coordinateWatcher.Start();

            GetGeoLocation(To, (l) => toLocation = l);
        }

        private void OnWatcherStatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            var coordinateWatcher = sender as IGeoPositionWatcher<GeoCoordinate>;
            if (e.Status == GeoPositionStatus.Ready)
            {
                coordinateWatcher.Stop();

                // Get position.
                fromLocation = coordinateWatcher.Position.Location;
                //if (fromLocation
                LocationLoaded();
            }
        }

        /// <summary>
        /// Calculate the route when the location of the start and end destination have been loaded.
        /// </summary>
        private void LocationLoaded()
        {
            if (fromLocation != null && toLocation != null)
            {
                var fromWaypoint = new Waypoint();
                fromWaypoint.Description = "From";
                fromWaypoint.Location = new Location();
                fromWaypoint.Location.Altitude = fromLocation.Altitude;
                fromWaypoint.Location.Latitude = fromLocation.Latitude;
                fromWaypoint.Location.Longitude = fromLocation.Longitude;

                var toWaypoint = new Waypoint();
                toWaypoint.Description = "To";
                toWaypoint.Location = new Location();
                toWaypoint.Location.Altitude = toLocation.Altitude;
                toWaypoint.Location.Latitude = toLocation.Latitude;
                toWaypoint.Location.Longitude = toLocation.Longitude;

                // Create the request.
                var routeRequest = new RouteRequest();
                routeRequest.Credentials = new Credentials();
                routeRequest.Credentials.ApplicationId = BingApiKey;
                routeRequest.Waypoints = new System.Collections.ObjectModel.ObservableCollection<Waypoint>();
                routeRequest.Waypoints.Add(fromWaypoint);
                routeRequest.Waypoints.Add(toWaypoint);
                routeRequest.Options = new RouteOptions();
                routeRequest.Options.RoutePathType = RoutePathType.Points;
                routeRequest.Options.Mode = _travelMode;
                routeRequest.UserProfile = new Utils.WP7.Bing.BingRoute.UserProfile();
                routeRequest.UserProfile.DistanceUnit = Utils.WP7.Bing.BingRoute.DistanceUnit.Kilometer;

                // Execute the request.
                var routeClient = new RouteServiceClient("BasicHttpBinding_IRouteService");
                routeClient.CalculateRouteCompleted += new EventHandler<CalculateRouteCompletedEventArgs>(OnRouteComplete);
                routeClient.CalculateRouteAsync(routeRequest);
            }
        }

        /// <summary>
        /// Route calculated, show in the UI.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnRouteComplete(object sender, CalculateRouteCompletedEventArgs e)
        {
            IsBusy = false;
            if (e.Result != null && e.Result.Result != null && e.Result.Result.Legs != null & e.Result.Result.Legs.Any())
            {
                var result = e.Result.Result;
                var legs = result.Legs.FirstOrDefault();

                StartPoint = legs.ActualStart;
                EndPoint = legs.ActualEnd;
                RoutePoints = result.RoutePath.Points;
                Itinerary = legs.Itinerary;

                RaiseRouteResolved();
            }
        }

        /// <summary>
        /// Find the location for an address.
        /// </summary>
        /// <param name="address"></param>
        /// <param name="callBack"></param>
        private void GetGeoLocation(Address address, Action<Utils.WP7.Bing.BingGeo.GeocodeLocation> callBack)
        {
            // Create the request.
            var geoRequest = new GeocodeRequest();
            geoRequest.Credentials = new Credentials();
            geoRequest.Credentials.ApplicationId = BingApiKey;
            geoRequest.Address = address;

            // Execute the request and display the results.
            var geoClient = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
            geoClient.GeocodeCompleted += (s, e) =>
            {
                callBack(e.Result.Results.FirstOrDefault().Locations.FirstOrDefault());
                LocationLoaded();
            };
            geoClient.GeocodeAsync(geoRequest);
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void Change(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
        #endregion
    }
}
